namespace Plugins.Engine.Internal
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using Plugins.Engine;
    using Plugins.Interfaces;
    using Plugins.Library;
    using Plugins.Serializer;

    [Serializable]
    internal sealed class PluginContext : IPluginContextInternal
    {
        private static readonly IPluginStringSerializer<byte[]> serializer = new PluginBinaryStringSerializer<byte[]>();

        private readonly IPluginAssemblyActivatorInternal _activator;
        private readonly string _applicationPath;
        private readonly string _fileName;
        private readonly byte[] _publicKeyToken;
        private Dictionary<Guid, IPluginObjectInternal> _hashtable = new Dictionary<Guid, IPluginObjectInternal>();
        private readonly IPluginHostInternal _host;
        private readonly IPluginAssemblyConfiguration _plugin;

        internal PluginContext(PluginHost host, IPluginAssemblyConfiguration plugin)
        {
            this._host = host;
            if (!string.IsNullOrEmpty(plugin.Name))
            {
                this._plugin = plugin;
                try
                {
                    this._applicationPath = AppDomain.CurrentDomain.BaseDirectory;
                    this._fileName = Path.Combine(this._applicationPath, plugin.Name);
                    this._publicKeyToken = PluginContext.GetPublicKeyToken(plugin.PublicKeyToken);
                }
                catch (ArgumentNullException ex)
                {
                    throw new PluginDomainException(ex);
                }
                catch (ArgumentException ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            this._activator = new PluginObjectActivator(string.Format(CultureInfo.InvariantCulture, "[{0},{1}]", this._host.Name, _plugin.Name));
        }

        private static byte[] GetPublicKeyToken(string publicKeyToken)
        {
            if (publicKeyToken.Length == 18)
            {
                byte[] result = serializer.Deserialize(string.Concat("0001000000ffffffff01000000000000000f010000000800000002", publicKeyToken));
                if (result == null)
                {
                    result = new byte[0];
                }
                return result;
            }
            throw new ArgumentException("publickeytoken");
        }

        [SuppressMessage("Microsoft.Performance", "CA1811")]
        private static string SetPublicKey(byte[] data)
        {
            if (data.Length == 8)
            {
                return serializer.Serialize(data).Remove(0, 54);
            }
            throw new ArgumentException("publickeytoken");
        }

        bool IPluginContextInternal.Load()
        {
            if (!this._activator.IsActive)
            {
                this._activator.Activate();
                try
                {
                    return this._activator.LoadAssembly(this._fileName, this._publicKeyToken);
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return false;
        }

        IPluginObjectInternal IPluginContextInternal.Load<T>(Type type)
        {
            if (this._activator.IsActive)
            {
                try
                {
                    Guid typeId = type.GUID;
                    IPluginObjectInternal obj = null;
                    IPluginContextInternal context = this;
                    List<Type> types = new List<Type>(type.BaseType.GetInterfaces());
                    if (this._hashtable.ContainsKey(typeId))
                    {
                        return this._hashtable[typeId];
                    }
                    if (!types.Contains(typeof(T)))
                    {
                        obj = new PluginObject<T>(type, context);
                        this._hashtable.Add(typeId, obj);
                        PluginLog.WriteAsync(string.Format(CultureInfo.InvariantCulture, "add object interface={0} type={1} : {2}", PluginTypeHelper.GetTypeName(typeof(T)), PluginTypeHelper.GetTypeName(type), PluginTypeHelper.GetTypeName(type.BaseType)));
                    }
                    return obj;
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return null;
        }

        bool IPluginContextInternal.Unload()
        {
            if (this._activator.IsActive)
            {
                this._activator.Deactivate();
                try
                {
                    return this._activator.UnloadAssembly(this._fileName, this._publicKeyToken);
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return false;
        }

        IPluginObjectInternal IPluginContextInternal.Unload<T>(Type type)
        {
            if (this._activator.IsActive)
            {
                try
                {
                    Guid typeId = type.GUID;
                    IPluginObjectInternal obj = null;
                    List<Type> types = new List<Type>(type.BaseType.GetInterfaces());
                    if (this._hashtable.ContainsKey(typeId) && !types.Contains(typeof(T)))
                    {
                        obj = this._hashtable[typeId] as IPluginObjectInternal;
                        this._hashtable.Remove(typeId);
                        PluginLog.WriteAsync(string.Format(CultureInfo.InvariantCulture, "remove object interface={0} type={1} : {2} ", PluginTypeHelper.GetTypeName(typeof(T)), PluginTypeHelper.GetTypeName(type), PluginTypeHelper.GetTypeName(type.BaseType)));
                    }
                    return obj;
                }
                catch (Exception ex)
                {
                    throw new PluginDomainException(ex);
                }
            }
            return null;
        }

        IPluginAssemblyInternal IPluginContextInternal.Assembly
        {
            get
            {
                return this._activator.Assembly;
            }
        }

        IPluginAssemblyConfiguration IPluginContextInternal.Configuration
        {
            get
            {
                return this._plugin;
            }
        }

        IPluginHostInternal IPluginContextInternal.Host
        {
            get
            {
                return this._host;
            }
        }

        bool IPluginContextInternal.IsLoaded
        {
            get
            {
                return this._activator.IsActive;
            }
        }
    }
}

